#include "slice.hpp"

Slice::Slice() {
  px=py=pz=0;
  start << px,py,pz;
  end << px,py,pz;
  dim << 0,0,0;
  volDim << 0,0,0;
  voxels=NULL;
  pos << 0,0,0;
  delta=1.0;
  orphan=true;
  offset << 0,0,0;
  offsetEnd=dim;
}

Slice::Slice(const Vector3i &dim) {
  px=py=pz=0;
  this->dim=dim;
  start << 0,0,0;
  end=dim;
  volDim=dim;
  voxels=new unsigned char[dim.prod()];
  pos << 0,0,0;
  delta=1.0;
  orphan=true;
  offset << 0,0,0;
  offsetEnd=dim;
}

Slice::Slice(const Vector3i &dim,unsigned char initial) {
  px=py=pz=0;
  this->dim=dim;
  start << 0,0,0;
  end=dim;
  volDim=dim;
  voxels=new unsigned char[dim.prod()];
  memset(voxels,initial,dim.prod());
  pos << 0,0,0;
  delta=1.0;
  orphan=true;
  offset << 0,0,0;
  offsetEnd=dim;
}

Slice::Slice(const Vector3i &start,const Vector3i &end,const Vector3i &volDim,double delta,
    const Vector3d &position,unsigned char *voxels,const Vector3i &offset) {
  this->start=start;
  px=start.x();
  py=start.y();
  pz=start.z();
  this->end=end;
  dim=end-start;
  this->volDim=volDim;
  this->voxels=voxels;
  this->delta=delta;
  pos=position+start.cast<double>()*delta;
  orphan=false;
  this->offset=offset;
  offsetEnd=offset+dim;
}

Slice::Slice(const Vector3i &start,const Vector3i &end,Volume &volume) {
  init(start,end,volume);
}

Slice::Slice(int startX,int startY,int startZ,int endX,int endY,int endZ,Volume &volume) {
  init(Vector3i(startX,startY,startZ),Vector3i(endX,endY,endZ),volume);
}

Slice::Slice(const Slice &other) {
  start << 0,0,0;
  px=start.x();
  py=start.y();
  pz=start.z();
  dim=other.size();
  end=start+dim;
  volDim=other.getVolumeSize();
  voxels=new unsigned char[dim.prod()];
  copy(other);
  pos=other.position();
  delta=other.getDelta();
  orphan=true;
  offset=other.getStart()+other.getOffset();
  offsetEnd=offset+dim;
}

Slice::~Slice() {
  if (orphan) {
    delete voxels;
  }
}

void Slice::init(const Vector3i &start,const Vector3i &end,Volume &volume) {
  this->start=start.cwiseMax(Vector3i(0,0,0));
  px=start.x();
  py=start.y();
  pz=start.z();
  volDim=volume.size();
  this->end=end.cwiseMin(volDim);
  dim=this->end-this->start;
  voxels=volume.data();
  delta=volume.getVoxelSize();
  pos=volume.position()+start.cast<double>()*delta;
  orphan=false;
  offset << 0,0,0;
  offsetEnd=offset+dim;
}

const Vector3i & Slice::getIndex() const {
  index << px,py,pz;
  return index;
}

void Slice::setIndex(const Vector3i &index) {
  px=index.x();
  py=index.y();
  pz=index.z();
}

int Slice::getFlatIndex() const {
  return pz*volDim.x()*volDim.y()+py*volDim.x()+px;
}

const Vector3i & Slice::getStart() const {
  return start;
}

const Vector3i & Slice::getEnd() const {
  return end;
}

const Vector3i & Slice::size() const {
  return dim;
}

const Vector3i & Slice::getVolumeSize() const {
  return volDim;
}

double Slice::getDelta() const {
  return delta;
}

const Vector3d & Slice::position() const {
  return pos;
}

unsigned char & Slice::value() {
  return voxels[pz*volDim.x()*volDim.y()+py*volDim.x()+px];
}

unsigned char & Slice::value(int index) {
  return voxels[index];
}

unsigned char & Slice::value(const Vector3i &index) {
  return voxels[index.z()*volDim.x()*volDim.y()+index.y()*volDim.x()+index.x()];
}

int & Slice::x() {
  return px;
}

int & Slice::y() {
  return py;
}

int & Slice::z() {
  return pz;
}

unsigned char * Slice::data() const {
  return voxels;
}

void Slice::copy(const Slice &other) {
  assert(dim==other.size());
  for (int j=0;j<dim.y();j++) {
    for (int k=0;k<dim.z();k++) {
      memcpy(data(0,j,k),other.data(0,j,k),dim.x());
    }
  }
}

Slice & Slice::operator=(const Slice &other) {
  if (voxels==NULL) {
    start << 0,0,0;
    px=start.x();
    py=start.y();
    pz=start.z();
    dim=other.size();
    end=start+dim;
    volDim=dim;
    voxels=new unsigned char[dim.prod()];
    //pos=other.position();
    delta=other.getDelta();
    orphan=true;
    offset=other.getOffset()+other.getStart();
    offsetEnd=end;
  }
  copy(other);
  return *this;
}

Slice & Slice::operator=(Volume &volume) {
  assert(dim==volume.size());
  for (int j=0;j<dim.y();j++) {
    for (int k=0;k<dim.z();k++) {
      memcpy(data(0,j,k),volume.data(0,j,k),dim.x());
    }
  }
  return *this;
}

Slice Slice::slice(const Vector3i &start,const Vector3i &end) {
  return Slice(this->start+start.cwiseMax(Vector3i(0,0,0)),(this->start+end).cwiseMin(this->end),
      volDim,delta,pos,voxels,offset);
}

unsigned char * Slice::data(int x,int y, int z) const {
  int offset=(start.z()+z)*volDim.x()*volDim.y()+(start.y()+y)*volDim.x()+(start.x()+x);
  return voxels+offset;
}

void Slice::setOffset(const Vector3i &offset) {
  this->offset=offset;
  offsetEnd=offset+dim;
}

const Vector3i & Slice::getOffset() const {
  return offset;
}

void Slice::replace(const Slice &other) {
  if (orphan) {
    delete voxels;
  }
  start=other.getStart();
  px=start.x();
  py=start.y();
  pz=start.z();
  end=other.getEnd();
  dim=end-start;
  volDim=other.getVolumeSize();
  if (other.isOrphan()) {
    voxels=new unsigned char[dim.prod()];
    copy(other);
  } else {
    voxels=other.data();
  }
  delta=other.getDelta();
  pos=other.position()+start.cast<double>()*delta;
  orphan=false;
  offset=other.getOffset();
  //offsetEnd=end;
}

bool Slice::isOrphan() const {
  return orphan;
}
